#include "LightColor.h"
#include "Tools.h"

#include <app-common/zap-generated/ids/Attributes.h>
#include <app-common/zap-generated/ids/Clusters.h>

using namespace app::Clusters;
using namespace app::Clusters::ColorControl::Attributes;
using namespace tools;

namespace {

constexpr DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(colorControlAttrs)
    DECLARE_DYNAMIC_ATTRIBUTE(CurrentHue::Id, INT8U, 1, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(CurrentSaturation::Id, INT8U, 1, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(RemainingTime::Id, INT16U, 1, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(ColorTemperatureMireds::Id, INT16U, 2, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(ColorMode::Id, ENUM8, 1, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(Options::Id, BITMAP8, 1, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(NumberOfPrimaries::Id, INT8U, 1, 0),

    DECLARE_DYNAMIC_ATTRIBUTE(EnhancedCurrentHue::Id, BITMAP8, 1, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(EnhancedColorMode::Id, BITMAP8, 1, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(ColorLoopActive::Id, BITMAP8, 1, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(ColorLoopDirection::Id, BITMAP8, 1, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(ColorLoopTime::Id, INT16U, 2, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(ColorLoopStartEnhancedHue::Id, INT16U, 2, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(ColorLoopStoredEnhancedHue::Id, INT16U, 2, 0),

    DECLARE_DYNAMIC_ATTRIBUTE(ColorCapabilities::Id, BITMAP16, 2, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(ColorTempPhysicalMinMireds::Id, INT16U, 2, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(ColorTempPhysicalMaxMireds::Id, INT16U, 2, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(CoupleColorTempToLevelMinMireds::Id, INT16U, 2, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(StartUpColorTemperatureMireds::Id, INT16U, 2, 0),
    DECLARE_DYNAMIC_ATTRIBUTE(FeatureMap::Id, BITMAP32, 4, 0),
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();


constexpr CommandId colorControlIncomingCommands[] = {
        ColorControl::Commands::MoveToHue::Id,
        ColorControl::Commands::MoveHue::Id,
        ColorControl::Commands::StepHue::Id,
        ColorControl::Commands::MoveToSaturation::Id,
        ColorControl::Commands::MoveSaturation::Id,
        ColorControl::Commands::StepSaturation::Id,
        ColorControl::Commands::MoveToHueAndSaturation::Id,
        ColorControl::Commands::MoveToColor::Id,
        ColorControl::Commands::MoveColor::Id,
        ColorControl::Commands::StepColor::Id,
        ColorControl::Commands::EnhancedMoveToHue::Id,
        ColorControl::Commands::EnhancedMoveHue::Id,
        ColorControl::Commands::EnhancedStepHue::Id,
        ColorControl::Commands::EnhancedMoveToHueAndSaturation::Id,
        ColorControl::Commands::ColorLoopSet::Id,
        ColorControl::Commands::StopMoveStep::Id,
        ColorControl::Commands::MoveToColorTemperature::Id,
        ColorControl::Commands::StepColorTemperature::Id,
        kInvalidCommandId,
    };

EmberAfCluster colorControlCluster = DECLARE_DYNAMIC_CLUSTER(ColorControl::Id, colorControlAttrs,
                                                             ZAP_CLUSTER_MASK(SERVER), colorControlIncomingCommands, nullptr);
}


LightColor::LightColor(const ::BasicInformation* node)
    : LightDimmer(node) {
    mClusters.push_back(colorControlCluster);

    mCurrentHue = 0;
    mCurrentSaturation = 0xfe;

    mCurrentCT = static_cast<uint16_t>(1000000 / 2700);

//    mColorMode = uint8_t(ColorControl::ColorMode::kCurrentHueAndCurrentSaturation);
    mColorMode = static_cast<uint8_t>(ColorControl::ColorMode::kColorTemperature);
    // mColorMode = ColorControl::ColorMode::EMBER_ZCL_COLOR_MODE_COLOR_TEMPERATURE;

    Json::Value capabilities = node->GetExtra()["capabilities"];
    for (const auto& capability : capabilities) {
        ChipLogDetail(DeviceLayer, "capability: %s", capability.asString().c_str());
        if (capability.asString() == "HueSaturation") {
            mColorCapabilities |= static_cast<uint16_t>(ColorControl::ColorCapabilities::kHueSaturationSupported);
        } else if (capability.asString() == "ColorTemperature") {
            mColorCapabilities |= static_cast<uint16_t>(ColorControl::ColorCapabilities::kColorTemperatureSupported);
        }
    }
    ChipLogDetail(DeviceLayer, "%s capability: %04x", node->GetLabel().c_str(), mColorCapabilities);
}

Protocols::InteractionModel::Status LightColor::ReadAttribute(
    ClusterId clusterId,
    AttributeId attributeId,
    uint8_t* buffer,
    uint16_t maxReadLength) {
    if (clusterId != ColorControl::Id) {
        return LightDimmer::ReadAttribute(clusterId, attributeId, buffer, maxReadLength);
    }

    ChipLogProgress(DeviceLayer, "ReadAttribute: attrId=%d, maxReadLength=%d", attributeId, maxReadLength);
    if (attributeId == CurrentHue::Id) {
        GetAttr(buffer, maxReadLength, mCurrentHue);
    } else if (attributeId == CurrentSaturation::Id) {
        GetAttr(buffer, maxReadLength, mCurrentSaturation);
    } else if (attributeId == ColorTemperatureMireds::Id) {
        GetAttr(buffer, maxReadLength, mCurrentCT);
    } else if (attributeId == ColorTempPhysicalMinMireds::Id) {
        GetAttr(buffer, maxReadLength, static_cast<uint16_t>(1000000 / 6500));
    } else if (attributeId == ColorTempPhysicalMaxMireds::Id) {
        GetAttr(buffer, maxReadLength, static_cast<uint16_t>(1000000 / 2700));
    } else if (attributeId == CoupleColorTempToLevelMinMireds::Id) {
        GetAttr(buffer, maxReadLength, static_cast<uint16_t>(1000000 / 50));
    } else if (attributeId == RemainingTime::Id) {
        GetAttr(buffer, maxReadLength, mRemainingTime);
    } else if (attributeId == ColorMode::Id
        || attributeId == EnhancedColorMode::Id) {
        GetAttr(buffer, maxReadLength, mColorMode);
    } else if (attributeId == Options::Id) {
        GetAttr(buffer, maxReadLength, static_cast<uint8_t>(0));
    } else if (attributeId == ColorCapabilities::Id) {
        GetAttr(buffer, maxReadLength, mColorCapabilities);
    }
    // FeatureMap
    else if (attributeId == FeatureMap::Id) {
        GetAttr(buffer, maxReadLength, static_cast<uint32_t>(mColorCapabilities));
    } else {
        memset(buffer, 0x00, maxReadLength);
    }
    return Protocols::InteractionModel::Status::Success;;
}

void LightColor::UpdateColorMode(uint8_t mode) {
    const bool changed = mColorMode != mode;
    mColorMode = mode;

    ChipLogProgress(DeviceLayer, "Device[%s]: ColorMode = %s",
                    GetName().c_str(),
                    mColorMode == static_cast<uint8_t>(ColorControl::ColorMode::kCurrentHueAndCurrentSaturation) ? "ColorTemperature" :
                    "HueSaturation");

    if (changed) {
        NotifyStatusChanged(ColorControl::Id, ColorMode::Id);
    }
}

void LightColor::UpdateColorTemperature(uint16_t value) {
    if (value == mCurrentCT) {
        return;
    }
    mCurrentCT = value;
    ChipLogProgress(DeviceLayer, "mCurrentCT:%d -->> %d", mCurrentCT,
                    static_cast<int>(1000000u) / static_cast<int>(mCurrentCT));
    NotifyStatusChanged(ColorControl::Id, ColorTemperatureMireds::Id);
    UpdateColorMode(static_cast<uint8_t>(ColorControl::ColorMode::kColorTemperature));
}

void LightColor::UpdateHueAndSaturation(uint8_t newHuValue, uint8_t newSaturationValue) {
    if (abs(newHuValue - this->mCurrentHue) > 2) {
        const auto oldHueValue = this->mCurrentHue;
        this->mCurrentHue = newHuValue;
        ChipLogProgress(DeviceLayer, "mCurrentHue from:%d to:%d ", oldHueValue, mCurrentHue);
        NotifyStatusChanged(ColorControl::Id, CurrentHue::Id);
    }

    if (abs(newSaturationValue - this->mCurrentSaturation) > 2) {
        const auto oldSaturationValue = this->mCurrentSaturation;
        this->mCurrentSaturation = newSaturationValue;
        ChipLogProgress(DeviceLayer, "mCurrentSaturation from:%d to:%d", oldSaturationValue, mCurrentSaturation);
        NotifyStatusChanged(ColorControl::Id, CurrentSaturation::Id);
    }
    UpdateColorMode(static_cast<uint8_t>(ColorControl::ColorMode::kCurrentHueAndCurrentSaturation));
}


void LightColor::SetColorTemperature(uint16_t value) {
    if (value == mCurrentCT) {
        return;
    }

    auto block = DataBlock(Path(ColorControl::Id, ColorTemperatureMireds::Id), value);
    SendMessage(&block);

    UpdateColorTemperature(value);
}

void LightColor::SetHueAndSaturation(uint8_t hue, uint8_t saturation) {
    mCurrentSaturation = hue;
    mCurrentSaturation = saturation;
    ChipLogProgress(DeviceLayer, "mCurrentStation:%d", mCurrentSaturation);
    NotifyStatusChanged(ColorControl::Id, CurrentHue::Id);
    NotifyStatusChanged(ColorControl::Id, CurrentSaturation::Id);
    UpdateColorMode(static_cast<uint8_t>(ColorControl::ColorMode::kCurrentHueAndCurrentSaturation));

    {
        auto block = DataBlock(Path(ColorControl::Id, CurrentHue::Id), hue);
        SendMessage(&block);
    }
    {
        auto block = DataBlock(Path(ColorControl::Id, CurrentSaturation::Id), saturation);
        SendMessage(&block);
    }
}


Protocols::InteractionModel::Status LightColor::WriteAttribute(
    ClusterId clusterId,
    const EmberAfAttributeMetadata* attributeMetadata,
    uint8_t* buffer) {
    if (clusterId != ColorControl::Id) {
        return LightDimmer::WriteAttribute(clusterId, attributeMetadata, buffer);
    }

    ChipLogProgress(DeviceLayer, "AttributeWrite attribute:%x", attributeMetadata->attributeId);
    if (attributeMetadata->attributeId == CurrentHue::Id) {
        this->mCurrentHue = *buffer;
        ChipLogProgress(DeviceLayer, "mCurrentHue:%d", mCurrentHue);
    } else if (attributeMetadata->attributeId == CurrentSaturation::Id) {
        SetHueAndSaturation(mCurrentHue, *buffer);
    } else if (attributeMetadata->attributeId == ColorTemperatureMireds::Id) {
        SetColorTemperature(*reinterpret_cast<uint16_t*>(buffer));
    } else if (attributeMetadata->attributeId == RemainingTime::Id) {
        mRemainingTime = *reinterpret_cast<uint16_t*>(buffer);
        ChipLogProgress(DeviceLayer, "RemainingTime:%d", mRemainingTime);
        NotifyStatusChanged(ColorControl::Id, RemainingTime::Id);
    } else {
        return Protocols::InteractionModel::Status::UnsupportedWrite;
    }

    return Protocols::InteractionModel::Status::Success;
}

ChipError LightColor::OnReport(const DataBlock* block) {
    if (block->path.clusterId != ColorControl::Id) {
        return LightDimmer::OnReport(block);
    }

    if (block->path.attributeId == CurrentHue::Id) {
        UpdateHueAndSaturation(static_cast<uint8_t>(block->data.asInt()), mCurrentSaturation);
    } else if (block->path.attributeId == CurrentSaturation::Id) {
        UpdateHueAndSaturation(mCurrentHue, static_cast<uint8_t>(block->data.asInt()));
    } else if (block->path.attributeId == ColorTemperatureMireds::Id) {
        UpdateColorTemperature(static_cast<uint16_t>(block->data.asInt()));
    }
    return CHIP_NO_ERROR;
}
